/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alimuzaffar.lib.pin;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.TextField;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * PinEntryEditText
 *
 * @since 2021-06-28
 *
 */
public class PinEntryEditText extends TextField implements Component.DrawTask,Component.EstimateSizeListener,
    Component.BindStateChangedListener {
    private static final String DEFAULT_MASK = "\u25CF";



    private static final int ONE_HUNDRED_AND_FIFTY_THREE  = 153;
    private static final int ONE_HUNDRED_AND_SEVENTY  = 170;
    private static final int TWO_HUNDRED_AND_FOUR = 204;
    private static final int TWO_HUNDRED_AND_FIFTY_FIVE = 255;
    private static final int THREE_HUNDRED = 300;

    private String mMask = null;
    private boolean isMask;
    private StringBuilder mMaskChars = null;
    private String mSingleCharHint = null;
    private int mAnimatedType = 0;
    private float mSpace = 24; // 24 vp by default, space between the lines
    private float mCharSize;
    private float mNumChars = 4;
    private float mTextBottomPadding = 8; // 8vp by default, height of the text from our lines
    private int mMaxLength = 4;
    private RectFloat[] mLineCoords;
    private float[] mCharBottom;
    private Paint mCharPaint;
    private Paint mLastCharPaint;
    private Paint mSingleCharPaint;
    private ShapeElement mPinBackground;
    private Rect mTextHeight = new Rect();
    private boolean mIsDigitSquare = false;

    private OnPinEnteredListener mOnPinEnteredListener = null;

    private float mLineStroke = 2.5f;
    private float mLineStrokeSelected = 5f;
    private Paint mLinesPaint;
    private boolean mAnimate = false;
    private boolean mHasError = false;

    private CharSequence inputText;
    private int textSize;
    private boolean haveBackground;
    private int start = 0;
    private int inputTextSize;

    /**
     * 构造方法 PinEntryEditText构造方法
     *
     * @param context context
     */
    public PinEntryEditText(Context context) {
        super(context);
    }

    /**
     * 构造方法 PinEntryEditText构造方法
     *
     * @param context context
     * @param attrSet attrSet
     */
    public PinEntryEditText(Context context, AttrSet attrSet) {
        super(context, attrSet);

        // 设置测量组件的侦听器
        setEstimateSizeListener(this);
        setBindStateChangedListener(this);
        init(context,attrSet);

        // 添加绘制任务
        addDrawTask(this);
    }

    /**
     * 构造方法 PinEntryEditText构造方法
     *
     * @param context context
     * @param attrSet attrSet
     * @param styleName styleName
     */
    public PinEntryEditText(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
    }

    /**
     * 初始化
     *
     * @param context context
     * @param attrSet attrSet
     */
    private void init(Context context, AttrSet attrSet) {
        mNumChars = AttrValue.get(attrSet, "maxLength", 4);
        mMaxLength = (int)mNumChars;
        mIsDigitSquare = AttrValue.get(attrSet, "pinBackgroundIsSquare", mIsDigitSquare);
        haveBackground = AttrValue.get(attrSet,"haveBackground",false);
        isMask = AttrValue.get(attrSet, "haveMask", false);
        mSpace =  AttrValue.getDimension(attrSet, "pinCharacterSpacing", AttrHelper.fp2px(mSpace, getContext()));
        mTextBottomPadding =  AttrValue.getDimension(attrSet, "pinTextBottomPadding",
            AttrHelper.fp2px(mTextBottomPadding, getContext()));
        mSingleCharHint = AttrValue.get(attrSet, "pinRepeatedHint", "");
        mAnimatedType = AttrValue.get(attrSet, "animatedType", 0);

        inputTextSize = AttrValue.get(attrSet, "inputTextSize", 40);
        if (isMask) {
            if (TextTool.isNullOrEmpty(mMask)) {
                mMask = DEFAULT_MASK;
            }
        }
        mCharPaint = new Paint();
        mLastCharPaint = new Paint();
        mSingleCharPaint = new Paint();
        mLinesPaint = new Paint();
        mLinesPaint.setStrokeWidth(mLineStroke);
        mPinBackground = new ShapeElement();

        Font font = createFontBuild(getContext(),"charmonman_regular.ttf");
        if (!haveBackground) {
            mCharPaint.setFont(font);
            mLastCharPaint.setFont(font);
        }
        mSingleCharPaint.setFont(font);

        setBackground(null);
        if (!TextTool.isNullOrEmpty(mMask)) {
            mMaskChars = getMaskChars();
        }
        mAnimate = mAnimatedType > -1;

        setListener();

        // 取消输入框的Bubble气泡样式
        cancelBubble();

        // 隐藏原生光标
        setTextCursorVisible(false);

        // 设置单行输入
        setMultipleLine(false);

        // 通过反射改变光标TextSelectHandle的样式
        setSelectionColor(Color.TRANSPARENT);
    }

    private void setListener() {
        this.setCursorChangedListener(new CursorChangedListener() {
            @Override
            public void onCursorChange(TextField textField, int num, int numb) {
                inputText = getFullText();
                textSize = inputText.length();
                onTextChanged();
            }
        });
        this.setComponentStateChangedListener(new ComponentStateChangedListener() {
            @Override
            public void onComponentStateChanged(Component component, int num) {
                addDrawTask(PinEntryEditText.this);
            }
        });
    }

    /**
     * onComponentUnboundFromWindow
     *
     * @param component component
     */
    @Override
    public void onComponentUnboundFromWindow(Component component) {

    }

    /**
     * onComponentBoundToWindow
     *
     * @param component component
     */
    @Override
    public void onComponentBoundToWindow(Component component) {
        onSizeChanged(this);
    }

    /**
     * onRtlChanged
     *
     * @param layoutDirection layoutDirection
     */
    @Override
    protected void onRtlChanged(LayoutDirection layoutDirection) {
        super.onRtlChanged(layoutDirection);
    }

    /**
     * 取消Bubble样式
     *
     */
    private void cancelBubble() {
        ShapeElement bubbleBackground = new ShapeElement();
        bubbleBackground.setBounds(1,1,1,1);
        this.setBubbleElement(bubbleBackground);
    }

    /**
     * 文本变化
     *
     * @param component  组件
     */
    private void onSizeChanged(Component component) {
        mLastCharPaint.setColor(Color.BLACK);
        mLastCharPaint.setTextSize(inputTextSize);
        mCharPaint.setColor(Color.BLACK);
        mCharPaint.setTextSize(inputTextSize);
        mSingleCharPaint.setColor(new Color(0xFFCCCCCC));
        mSingleCharPaint.setTextSize(inputTextSize);
        if (!haveBackground) {
            mLastCharPaint.setColor(new Color(0xFF333840));
            mCharPaint.setColor(new Color(0xFF333840));
        }

        int availableWidth = getWidth() - this.getPaddingEnd() - this.getPaddingStart();
        if (mSpace < 0) {
            mCharSize = availableWidth / (mNumChars * 2 - 1);
        } else {
            mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
        }
        mLineCoords = new RectFloat[(int) mNumChars];
        mCharBottom = new float[(int) mNumChars];
        int startX;
        int bottom = getHeight() - getPaddingBottom();
        int rtlFlag;
        final boolean isLayoutRtl = this.getLayoutDirection() == LayoutDirection.RTL;
        if (isLayoutRtl) {
            rtlFlag = -1;
            startX = (int) (getWidth() - this.getPaddingStart() - mCharSize);
        } else {
            rtlFlag = 1;
            startX = this.getPaddingStart();
        }
        for (int i = 0; i < mNumChars; i++) {
            mLineCoords[i] = new RectFloat(startX, bottom, startX + mCharSize, bottom);
            if (haveBackground) {
                if (mIsDigitSquare) {
                    mLineCoords[i].top = getPaddingTop();
                    mLineCoords[i].right = startX + mLineCoords[i].getWidth();
                } else {
                    mLineCoords[i].top -= mTextHeight.getHeight() + mTextBottomPadding * 2;
                }
            }
            if (mSpace < 0) {
                startX += rtlFlag * mCharSize * 2;
            } else {
                startX += rtlFlag * (mCharSize + mSpace);
            }
            mCharBottom[i] = mLineCoords[i].bottom - mTextBottomPadding;
        }
    }

    /**
     * onEstimateSize
     *
     * @param widthMeasureSpec widthMeasureSpec
     * @param heightMeasureSpec heightMeasureSpec
     * @return boolean
     */
    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        if (mIsDigitSquare) {
            int widthMode = EstimateSpec.getMode(widthMeasureSpec);
            int heightMode = EstimateSpec.getMode(heightMeasureSpec);
            int measuredWidth = 0;
            int measuredHeight = 0;

            // If we want a square or circle pin box, we might be able
            // to figure out the dimensions outselves
            // if width and height are set to wrap_content or match_parent
            if (widthMode == EstimateSpec.PRECISE) {
                measuredWidth = EstimateSpec.getSize(widthMeasureSpec);
                measuredHeight = (int) ((measuredWidth - (mNumChars - 1 * mSpace)) / mNumChars);
            } else if (heightMode == EstimateSpec.PRECISE) {
                measuredHeight = EstimateSpec.getSize(heightMeasureSpec);
                measuredWidth = (int) ((measuredHeight * mNumChars) + (mSpace * mNumChars - 1));
            } else if (widthMode == EstimateSpec.NOT_EXCEED) {
                measuredWidth = EstimateSpec.getSize(widthMeasureSpec);
                measuredHeight = (int) ((measuredWidth - (mNumChars - 1 * mSpace)) / mNumChars);
            } else if (heightMode == EstimateSpec.NOT_EXCEED) {
                measuredHeight = EstimateSpec.getSize(heightMeasureSpec);
                measuredWidth = (int) ((measuredHeight * mNumChars) + (mSpace * mNumChars - 1));
            } else {
                // Both unspecific
                // Try for a width based on our minimum
                measuredWidth = getPaddingLeft() + getPaddingRight() + getMinWidth();

                // Whatever the width ends up being, ask for a height that would let the pie
                // get as big as it can
                measuredHeight = (int) ((measuredWidth - (mNumChars - 1 * mSpace)) / mNumChars);
            }
            setEstimatedSize(
                EstimateSpec.getChildSizeWithMode(measuredWidth, measuredWidth,
                    EstimateSpec.NOT_EXCEED),
                EstimateSpec.getChildSizeWithMode(measuredHeight, measuredHeight,
                    EstimateSpec.NOT_EXCEED));
        } else {
            int measuredWidth = EstimateSpec.getSize(widthMeasureSpec);
            int measuredHeight = EstimateSpec.getSize(heightMeasureSpec);
            setEstimatedSize(
                EstimateSpec.getChildSizeWithMode(measuredWidth, measuredWidth,
                    EstimateSpec.NOT_EXCEED),
                EstimateSpec.getChildSizeWithMode(measuredHeight, measuredHeight,
                    EstimateSpec.NOT_EXCEED));
        }
        return false;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        inputText = getFullText();
        int textLength = inputText.length();
        float[] textWidths = new float[textLength];

        float hintWidth = 0;
        if (mSingleCharHint != null) {
            float[] hintWidths = new float[mSingleCharHint.length()];
            for (float temp : hintWidths) {
                hintWidth += temp;
            }
        }
        for (int i = 0; i < mNumChars; i++) {
            // If a background for the pin characters is specified, it should be behind the characters.
            if (haveBackground) {
                updateDrawableState(i < textLength, i == textLength);
                mPinBackground.setBounds((int) mLineCoords[i].left, (int) mLineCoords[i].top,
                    (int) mLineCoords[i].right, (int) mLineCoords[i].bottom);
                mPinBackground.drawToCanvas(canvas);
            }
            float middle = mLineCoords[i].left + mCharSize / 2;
            if (textLength > i) {
                float startX = middle - textWidths[i] / 2;
                if (haveBackground && !isMask) {
                    startX -= 20;
                } else if (mSingleCharHint != null) {
                    char str = inputText.toString().substring(i,i + 1).charAt(0);
                    if (Character.isUpperCase(str)) {
                        startX -= 22;
                    } else if (isChineseChar(str)) {
                        startX -= 30;
                    } else {
                        startX -= 15;
                    }
                } else {
                    startX -= 10;
                }
                if (!mAnimate || i != textLength - 1) {
                    canvas.drawText(mCharPaint, inputText.toString().substring(i,i + 1), startX, mCharBottom[i] - 5);
                } else {
                    canvas.drawText(mLastCharPaint, inputText.toString().substring(i,i + 1),startX, mCharBottom[i] - 5);
                }
            } else if (mSingleCharHint != null) {
                canvas.drawText(mSingleCharPaint, mSingleCharHint,middle - hintWidth / 2 - 17, mCharBottom[i] - 5);
            }

            // The lines should be in front of the text (because that's how I want it).
            if (!haveBackground) {
                updateColorForLines(i <= textLength);
                canvas.drawLine(mLineCoords[i].left, mLineCoords[i].top - 5, mLineCoords[i].right,
                    mLineCoords[i].bottom - 5, mLinesPaint);
            }
        }
    }

    /**
     * 校验一个字符是否是汉字 被校验的字符
     *
     * @param str  字符
     * @return true代表是汉字
     */
    private boolean isChineseChar(char str) {
        try {
            return String.valueOf(str).getBytes("UTF-8").length > 1;
        } catch (UnsupportedEncodingException e) {
            return false;
        }
    }

    private void updateDrawableState(boolean hasText, boolean isNext) {
        if (mHasError) {
            mPinBackground.setCornerRadius(25);
            mPinBackground.setRgbColor(new RgbColor(TWO_HUNDRED_AND_FOUR,0,0));
            mPinBackground.setStroke(1,new RgbColor(TWO_HUNDRED_AND_FOUR,0,0));
        } else if (isFocused()) {
            mPinBackground.setCornerRadius(25);
            mPinBackground.setStroke(6,new RgbColor(TWO_HUNDRED_AND_FOUR,
                TWO_HUNDRED_AND_FOUR,TWO_HUNDRED_AND_FOUR));
            if (isMask) {
                mPinBackground.setStroke(3,new RgbColor(TWO_HUNDRED_AND_FOUR,
                    TWO_HUNDRED_AND_FOUR,TWO_HUNDRED_AND_FOUR));
            }
            if (isNext) {
                mPinBackground.setRgbColor(new RgbColor(TWO_HUNDRED_AND_FIFTY_FIVE,
                    TWO_HUNDRED_AND_FIFTY_FIVE,TWO_HUNDRED_AND_FIFTY_FIVE));
                mPinBackground.setStroke(6,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                    TWO_HUNDRED_AND_FOUR,0));
                if (isMask) {
                    mPinBackground.setStroke(3,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                        TWO_HUNDRED_AND_FOUR,0));
                }
            } else if (hasText) {
                mPinBackground.setRgbColor(new RgbColor(TWO_HUNDRED_AND_FOUR,
                    TWO_HUNDRED_AND_FOUR,TWO_HUNDRED_AND_FOUR));
                mPinBackground.setStroke(6,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                    TWO_HUNDRED_AND_FOUR,0));
                if (isMask) {
                    mPinBackground.setStroke(3,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                        TWO_HUNDRED_AND_FOUR,0));
                }
            }
        } else {
            setBackGround(hasText);
        }
    }

    private void setBackGround(boolean hasText){
        if (hasText) {
            mPinBackground.setCornerRadius(100);
            if (isMask) {
                mPinBackground.setRgbColor(new RgbColor(TWO_HUNDRED_AND_FIFTY_FIVE,
                    TWO_HUNDRED_AND_FIFTY_FIVE,TWO_HUNDRED_AND_FIFTY_FIVE));
            } else {
                mPinBackground.setRgbColor(new RgbColor(TWO_HUNDRED_AND_FOUR,
                    TWO_HUNDRED_AND_FOUR,TWO_HUNDRED_AND_FOUR));
            }
            mPinBackground.setStroke(6,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                TWO_HUNDRED_AND_FOUR,0));
            if (isMask) {
                mPinBackground.setStroke(3,new RgbColor(ONE_HUNDRED_AND_FIFTY_THREE,
                    TWO_HUNDRED_AND_FOUR,0));
            }
        } else {
            mPinBackground.setCornerRadius(25);
            mPinBackground.setRgbColor(new RgbColor(ONE_HUNDRED_AND_SEVENTY,
                ONE_HUNDRED_AND_SEVENTY,ONE_HUNDRED_AND_SEVENTY));
            mPinBackground.setStroke(1,new RgbColor(TWO_HUNDRED_AND_FIFTY_FIVE,
                TWO_HUNDRED_AND_FIFTY_FIVE,TWO_HUNDRED_AND_FIFTY_FIVE));
        }
    }

    /**
     * 设置动画类型
     *
     * @param animate 动画类型
     */
    public void setAnimateText(boolean animate) {
        mAnimate = animate;
    }

    /**
     * 设置输入错误的背景
     *
     * @param hasError 是否有错误
     */
    public void setError(boolean hasError) {
        mHasError = hasError;
        invalidate();
    }

    private void onTextChanged() {
        if (start >= inputText.length()) {
            start = inputText.length();
        } else {
            start = inputText.length() - 1;
        }
        if (start < 0) {
            start = 0;
        }
        int lengthBefore = 0;
        int lengthAfter = 0;

        if (textSize > start) {
            lengthBefore = 0;
            lengthAfter = 1;
        } else {
            lengthBefore = 1;
            lengthAfter = 0;
        }

        textSize = inputText.length();
        setError(false);
        if (mLineCoords == null || !mAnimate) {
            if (mOnPinEnteredListener != null && inputText.length() == mMaxLength) {
                mOnPinEnteredListener.onPinEntered(inputText);
            }
            return;
        }
        if (mAnimatedType == -1) {
            invalidate();
            return;
        }

        if (lengthAfter > lengthBefore) {
            if (mAnimatedType == 0) {
                animatePopIn();
            } else {
                if (start >= mMaxLength) {
                    start = mMaxLength - 1;
                }
                animateBottomUp(inputText, start);
            }
        }
    }


    private synchronized float getAnimatedValue(float fraction, float... values) {
        if (values == null || values.length == 0) {
            return 0;
        }
        if (values.length == 1) {
            return values[0] * fraction;
        } else {
            if (fraction == 1) {
                return values[values.length - 1];
            }
            float oneFraction = 1f / (values.length - 1);
            float offFraction = 0;
            for (int i = 0; i < values.length - 1; i++) {
                if (offFraction + oneFraction >= fraction) {
                    return values[i] + (fraction - offFraction) * (values.length - 1) * (values[i + 1] - values[i]);
                }
                offFraction += oneFraction;
            }
        }
        return 0;
    }


    private void animatePopIn() {
        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float vue) {
                float value = getAnimatedValue(vue,1f,inputTextSize);
                mLastCharPaint.setTextSize((int)value);
                PinEntryEditText.this.invalidate();
            }
        });

        animatorValue.setDuration(200);
        if (inputText.length() == mMaxLength && mOnPinEnteredListener != null) {
            animatorValue.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {

                }

                @Override
                public void onStop(Animator animator) {

                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
                    mOnPinEnteredListener.onPinEntered(inputText);
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
        }
        animatorValue.start();
    }

    private void animateBottomUp(CharSequence text, final int startX) {
        mCharBottom[startX] = mLineCoords[startX].bottom - mTextBottomPadding;

        AnimatorValue animUp = new AnimatorValue();
        animUp.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float vue) {
                float value = getAnimatedValue(vue,-1,100);
                mCharBottom[start] = value;
                PinEntryEditText.this.invalidate();
            }
        });

        animUp.setDuration(THREE_HUNDRED);

        mLastCharPaint.setAlpha(TWO_HUNDRED_AND_FIFTY_FIVE);
        AnimatorValue animAlpha = new AnimatorValue();
        animAlpha.setDuration(THREE_HUNDRED);
        animAlpha.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float vue) {
                float value = getAnimatedValue(vue,0, TWO_HUNDRED_AND_FIFTY_FIVE);
                mLastCharPaint.setAlpha(value);
            }
        });

        AnimatorGroup animatorGroup = new AnimatorGroup();
        if (text.length() == mMaxLength && mOnPinEnteredListener != null) {
            animatorGroup.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {

                }

                @Override
                public void onStop(Animator animator) {

                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
                    mOnPinEnteredListener.onPinEntered(inputText);
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
        }
        animatorGroup.runParallel(animUp, animAlpha);
        animatorGroup.start();
    }

    private CharSequence getFullText() {
        if (TextTool.isNullOrEmpty(mMask)) {
            return this.getText();
        } else {
            return getMaskChars();
        }
    }

    private StringBuilder getMaskChars() {
        if (mMaskChars == null) {
            mMaskChars = new StringBuilder();
        }
        int textLength = getText().length();
        while (mMaskChars.length() != textLength) {
            if (mMaskChars.length() < textLength) {
                mMaskChars.append(mMask);
            } else {
                mMaskChars.deleteCharAt(mMaskChars.length() - 1);
            }
        }
        return mMaskChars;
    }

    /**
     * Is the color for a character that has been typed or is the next character to be typed?
     *
     * @param hasTextOrIsNext 是否有下一个
     */
    private void updateColorForLines(boolean hasTextOrIsNext) {
        if (mHasError) {
            mLinesPaint.setColor(new Color(0xFFCCCCCC));
        } else if (isFocused()) {
            mLinesPaint.setStrokeWidth(mLineStrokeSelected);
            mLinesPaint.setColor(new Color(0xFF303F9F));
            if (hasTextOrIsNext) {
                mLinesPaint.setColor(new Color(0xFFFF4081));
            }
        } else {
            mLinesPaint.setStrokeWidth(mLineStroke);
            mLinesPaint.setColor(new Color(0xFFCCCCCC));
        }
    }

    /**
     * 设置获取输入文本的回调
     *
     * @param listener 回调接口
     */
    public void setOnPinEnteredListener(OnPinEnteredListener listener) {
        mOnPinEnteredListener = listener;
    }

    /**
     * 文本回调接口
     *
     * OnPinEnteredListener
     *
     * @since 2021-06-28
     */
    public interface OnPinEnteredListener {
        /**
         * onPinEntered
         *
         * @param str 返回字符
         */
        void onPinEntered(CharSequence str);
    }

    /**
     * 加载外部字体
     *
     * @param context context
     * @param name name
     * @return Font
     */
    private Font createFontBuild(Context context, String name) {
        ResourceManager resManager = context.getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry("resources/rawfile/" + name);
        Resource resource = null;
        try {
            resource = rawFileEntry.openRawFile();
        } catch (IOException e) {
            e.getMessage();
        }
        StringBuffer fileName = new StringBuffer(name);
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName.toString());
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            int index;
            byte[] bytes = new byte[1024];
            if (resource != null) {
                while ((index = resource.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, index);
                    outputStream.flush();
                }
            }
        } catch (FileNotFoundException e) {
            e.getMessage();
        } catch (IOException e) {
            e.getMessage();
        } finally {
            try {
                if (resource != null) {
                    resource.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.getMessage();
            }
        }
        Font.Builder builder = new Font.Builder(file);
        return builder.build();
    }
}
